import { useState, useEffect, useRef } from 'react';
// ============================================================
// LIBRO PER SORRIDERE — Sistema ordini scuole
// ============================================================
const FONT_LINK_ID = 'lps-fonts';
function useFonts() {
useEffect(() => {
if (document.getElementById(FONT_LINK_ID)) return;
const link = document.createElement('link');
link.id = FONT_LINK_ID;
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap';
document.head.appendChild(link);
}, []);
}
// ---------- Storage helpers (localStorage) ----------
function useStorageReady() {
return true;
}
const memoryStore = {};
async function safeGet(key) {
try {
const val = localStorage.getItem('lps_' + key);
return val ? JSON.parse(val) : null;
} catch (e) {
return memoryStore[key] ?? null;
}
}
async function safeSet(key, value) {
memoryStore[key] = value;
try {
localStorage.setItem('lps_' + key, JSON.stringify(value));
return true;
} catch (e) {
console.error('storage set failed', e);
return false;
}
}
// ---------- Demo seed data ----------
const DEMO_LIBRI = [
{ id: 'l1', titolo: 'Il mio primo libro di Magia', descrizione: 'Trucchi facili e divertenti per diventare un vero mago.', pagine: 96, prezzo: 8, copertina: '🎩', scorte: 25, venduti: 0, fasceEta: ['primaria_media'] },
{ id: 'l2', titolo: "I misteri dell'antico Egitto", descrizione: 'Dei, regine, miti e meraviglie della civiltà egizia.', pagine: 64, prezzo: 8, copertina: '🏺', scorte: 20, venduti: 0, fasceEta: ['primaria_media'] },
{ id: 'l3', titolo: 'Barzellette e indovinelli', descrizione: 'Per divertirsi ovunque: a casa, a scuola, in viaggio.', pagine: 64, prezzo: 8, copertina: '😂', scorte: 30, venduti: 0, fasceEta: ['infanzia', 'primaria_media'] },
{ id: 'l4', titolo: 'Corpo Umano', descrizione: 'Dalla testa ai piedi, i segreti del corpo umano.', pagine: 64, prezzo: 7, copertina: '🫀', scorte: 20, venduti: 0, fasceEta: ['primaria_media'] },
{ id: 'l5', titolo: 'Zanna Bianca', descrizione: 'Il classico di Jack London, ghiacci e avventura.', pagine: null, prezzo: 7, copertina: '🐺', scorte: 15, venduti: 0, fasceEta: ['primaria_media'] },
{ id: 'l6', titolo: 'Sudoku Facili', descrizione: 'Oltre 100 schemi facili per allenare la logica.', pagine: 96, prezzo: 8, copertina: '🔢', scorte: 20, venduti: 0, fasceEta: ['primaria_media'] },
];
const DEMO_VOLANTINI = [
{ id: 'v1', nome: 'Volantino Primarie — Settembre', libriIds: ['l1', 'l2', 'l3', 'l4'] },
{ id: 'v2', nome: 'Volantino Medie — Settembre', libriIds: ['l3', 'l5', 'l6'] },
];
const DEMO_ISTITUTI = [
{
id: 'ic-ilariaalpi', nome: 'Istituto Comprensivo di Sarzana — Ilaria Alpi', comune: 'Sarzana', via: 'Piazza Dino Ricchetti', cap: '19038',
plessi: [
{ nome: 'Infanzia Marinella', tipo: 'infanzia' },
{ nome: 'Infanzia Sarzanello', tipo: 'infanzia' },
{ nome: 'Infanzia San Lazzaro', tipo: 'infanzia' },
{ nome: 'Infanzia Sarzana Capoluogo', tipo: 'infanzia' },
{ nome: 'Infanzia Lidia Lalli', tipo: 'infanzia' },
{ nome: 'Primaria Nave', tipo: 'primaria_media' },
{ nome: 'Primaria Marinella', tipo: 'primaria_media' },
{ nome: 'Primaria Sarzana Capoluogo', tipo: 'primaria_media' },
{ nome: 'Primaria San Lazzaro', tipo: 'primaria_media' },
{ nome: 'Primaria Santa Caterina', tipo: 'primaria_media' },
{ nome: 'Primaria Ghiaia', tipo: 'primaria_media' },
{ nome: 'Primaria Bradia', tipo: 'primaria_media' },
{ nome: 'Secondaria Poggi/Carducci', tipo: 'primaria_media' },
],
volantinoId: 'v1', attivo: true,
},
{
id: 'ic-n7laspezia', nome: 'Istituto Comprensivo N. 7 — La Spezia', comune: 'La Spezia', via: 'Via del Canaletto 165', cap: '19100',
plessi: [
{ nome: 'Infanzia Canaletto', tipo: 'infanzia' },
{ nome: 'Infanzia Fossamastra', tipo: 'infanzia' },
{ nome: 'Primaria Pitelli', tipo: 'primaria_media' },
{ nome: 'Primaria D. Alighieri', tipo: 'primaria_media' },
{ nome: 'Primaria Canaletto - Carducci', tipo: 'primaria_media' },
{ nome: 'Secondaria Fontana - Formentini', tipo: 'primaria_media' },
],
volantinoId: 'v2', attivo: true,
},
];
const DEMO_PAYMENT_LINKS = { nexi: '', paypal: '' };
const ADMIN_CODE = '1234';
// ---------- Root ----------
function App() {
useFonts();
const storageReady = useStorageReady();
const [view, setView] = useState('parent');
const [istituti, setIstituti] = useState(null);
const [libri, setLibri] = useState(null);
const [volantini, setVolantini] = useState(null);
const [ordini, setOrdini] = useState(null);
const [paymentLinks, setPaymentLinks] = useState(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!storageReady) return;
(async () => {
const [i, l, v, o, p] = await Promise.all([
safeGet('istituti'), safeGet('libri'), safeGet('volantini'),
safeGet('ordini'), safeGet('paymentLinks'),
]);
setIstituti(i ?? DEMO_ISTITUTI);
setLibri(l ?? DEMO_LIBRI);
setVolantini(v ?? DEMO_VOLANTINI);
setOrdini(o ?? []);
setPaymentLinks(p ?? DEMO_PAYMENT_LINKS);
setLoaded(true);
if (!i) await safeSet('istituti', DEMO_ISTITUTI);
if (!l) await safeSet('libri', DEMO_LIBRI);
if (!v) await safeSet('volantini', DEMO_VOLANTINI);
if (!o) await safeSet('ordini', []);
if (!p) await safeSet('paymentLinks', DEMO_PAYMENT_LINKS);
})();
}, [storageReady]);
async function persistIstituti(next) { setIstituti(next); await safeSet('istituti', next); }
async function persistLibri(next) { setLibri(next); await safeSet('libri', next); }
async function persistVolantini(next) { setVolantini(next); await safeSet('volantini', next); }
async function persistOrdini(next) { setOrdini(next); await safeSet('ordini', next); }
async function persistPaymentLinks(next) { setPaymentLinks(next); await safeSet('paymentLinks', next); }
if (!loaded) {
return (
);
}
return (
{view === 'parent' ? (
) : (
)}
);
}
// ---------- Top switch ----------
function TopSwitch({ view, setView }) {
const [showAdminGate, setShowAdminGate] = useState(false);
const [code, setCode] = useState('');
const [error, setError] = useState('');
function tryEnterAdmin() {
if (code === ADMIN_CODE) { setShowAdminGate(false); setCode(''); setError(''); setView('admin'); }
else setError('Codice errato');
}
return (
📖
Libro per Sorridere
{view === 'admin' && setView('parent')}>Vai al modulo genitori }
{view === 'parent' && setShowAdminGate(true)}>Area gestione }
{showAdminGate && (
setShowAdminGate(false)}>
e.stopPropagation()}>
Accesso gestione
{ setCode(e.target.value); setError(''); }}
placeholder="Codice" style={styles.gateInput} autoFocus
onKeyDown={(e) => e.key === 'Enter' && tryEnterAdmin()} />
{error &&
{error}
}
Entra
Demo: 1234
)}
);
}
// ============================================================
// PARENT FLOW
// ============================================================
function ParentFlow({ istituti: tuttiGliIstituti, libri, volantini, ordini, setOrdini, setLibri, paymentLinks }) {
const istituti = tuttiGliIstituti.filter((i) => i.attivo !== false);
const [step, setStep] = useState(1);
const [icId, setIcId] = useState('');
const [tipoScuola, setTipoScuola] = useState('');
const [plesso, setPlesso] = useState('');
const [selezionati, setSelezionati] = useState({});
const [classe, setClasse] = useState('');
const [classeTouched, setClasseTouched] = useState(false);
const [nomeBambino, setNomeBambino] = useState('');
const [nomeBambinoTouched, setNomeBambinoTouched] = useState(false);
const [telefono, setTelefono] = useState('');
const [metodoPagamento, setMetodoPagamento] = useState('nexi');
const [submitting, setSubmitting] = useState(false);
const icSelezionato = istituti.find((i) => i.id === icId);
const volantino = icSelezionato ? volantini.find((v) => v.id === icSelezionato.volantinoId) : null;
const libriDelVolantino = volantino
? volantino.libriIds
.map((id) => libri.find((l) => l.id === id))
.filter(Boolean)
.filter((l) => l.attivo !== false)
.filter((l) => !l.fasceEta || l.fasceEta.length === 0 || l.fasceEta.includes(tipoScuola))
: [];
const libriScelti = Object.keys(selezionati).filter((id) => selezionati[id]).map((id) => libri.find((l) => l.id === id)).filter(Boolean);
const totale = libriScelti.reduce((s, l) => s + l.prezzo, 0);
const classeValida = classe.trim().length > 0;
const nomeBambinoValido = nomeBambino.trim().length > 0;
const plessiFiltrati = icSelezionato ? icSelezionato.plessi.filter((p) => p.tipo === tipoScuola) : [];
function toggleLibro(libro) {
const disponibili = libro.scorte - (libro.venduti || 0);
if (disponibili <= 0 && !selezionati[libro.id]) return;
setSelezionati((prev) => ({ ...prev, [libro.id]: !prev[libro.id] }));
}
function reset() {
setStep(1); setIcId(''); setTipoScuola(''); setPlesso(''); setSelezionati({});
setClasse(''); setClasseTouched(false); setNomeBambino(''); setNomeBambinoTouched(false); setTelefono(''); setMetodoPagamento('nexi');
}
async function confermaOrdine() {
if (!classeValida) { setClasseTouched(true); return; }
setSubmitting(true);
const nuovoOrdine = {
id: 'ord-' + Date.now(), data: new Date().toISOString(), istitutoId: icId,
istitutoNome: icSelezionato.nome, comune: icSelezionato.comune,
tipoScuola: tipoScuola === 'infanzia' ? 'Infanzia/Asilo' : 'Primaria/Media',
plesso,
classe: classe.trim(), bambino: nomeBambino.trim(), telefono: telefono.trim(),
libri: libriScelti.map((l) => ({ id: l.id, titolo: l.titolo, prezzo: l.prezzo })),
totale, metodoPagamento, stato: 'da pagare',
};
const next = [nuovoOrdine, ...ordini];
await setOrdini(next);
const libriAggiornati = libri.map((l) => {
const scelto = libriScelti.find((s) => s.id === l.id);
return scelto ? { ...l, venduti: (l.venduti || 0) + 1 } : l;
});
await setLibri(libriAggiornati);
setSubmitting(false);
const link = paymentLinks?.[metodoPagamento];
if (link) window.open(link, '_blank', 'noopener,noreferrer');
setStep(5);
}
if (istituti.length === 0) return
;
return (
{step === 1 && (
{istituti.map((ic) => (
{ setIcId(ic.id); setTipoScuola(''); setPlesso(''); setSelezionati({}); }}>
{ic.nome}
{ic.comune} · {ic.cap}
{ic.via} (segreteria)
))}
{icSelezionato && (
Il tuo bambino frequenta…
{ setTipoScuola('infanzia'); setPlesso(''); }}
>
Scuola Infanzia / Asilo
{ setTipoScuola('primaria_media'); setPlesso(''); }}
>
Scuola Primaria / Elementare / Media
)}
{icSelezionato && tipoScuola && (
Che scuola frequenta?
{plessiFiltrati.length === 0 ? (
Nessun plesso di questo tipo registrato per questo istituto.
) : (
{plessiFiltrati.map((p) => (
setPlesso(p.nome)}>
{p.nome}
))}
)}
)}
setStep(2)} nextDisabled={!icId || !tipoScuola || !plesso} nextLabel="Continua" />
)}
{step === 2 && (
{libriDelVolantino.length === 0 ? (
Nessun volantino assegnato a questa scuola al momento.
) : (
{libriDelVolantino.map((l) => {
const disponibili = l.scorte - (l.venduti || 0);
const esaurito = disponibili <= 0;
const scelto = !!selezionati[l.id];
return (
toggleLibro(l)} disabled={esaurito}>
{l.prezzo}€
{l.copertinaImg ? (
) : (
{l.copertina}
)}
{l.titolo}
{l.descrizione && {l.descrizione}
}
{l.pagine && {l.pagine} pag.
}
{esaurito ? (
Esaurito
) : (
{scelto ? '✕' : ''}
)}
);
})}
)}
{libriScelti.length > 0 && (
{libriScelti.length} libr{libriScelti.length === 1 ? 'o' : 'i'} selezionat{libriScelti.length === 1 ? 'o' : 'i'}
{totale}€
)}
setStep(1)} onNext={() => setStep(3)} nextDisabled={libriScelti.length === 0} nextLabel="Continua" />
)}
{step === 3 && (
Classe obbligatorio
setClasse(e.target.value)} onBlur={() => setClasseTouched(true)} placeholder="Es. 3A" autoFocus />
{classeTouched && !classeValida && Inserisci la classe per continuare.
}
Nome e cognome del bambino obbligatorio
setNomeBambino(e.target.value)} onBlur={() => setNomeBambinoTouched(true)} placeholder="Es. Giulia Bianchi" />
{nomeBambinoTouched && !nomeBambinoValido && Inserisci il nome del bambino per continuare.
}
Telefono facoltativo
setTelefono(e.target.value)} type="tel" />
setStep(2)}
onNext={() => { setClasseTouched(true); setNomeBambinoTouched(true); if (classeValida && nomeBambinoValido) setStep(4); }}
nextDisabled={!classeValida || !nomeBambinoValido} nextLabel="Vai al pagamento" />
)}
{step === 4 && (
{libriScelti.map((l) =>
)}
Come vuoi pagare?
setMetodoPagamento('nexi')}>
Carta / Apple Pay / Google Pay
Incluso PostePay e altre carte · circuito Nexi
setMetodoPagamento('paypal')}>
PayPal
Paga con il tuo account PayPal
I libri verranno consegnati a scuola, direttamente in classe. Premendo «Conferma e paga» confermi l'ordine e verrai indirizzato alla pagina di pagamento sicura {metodoPagamento === 'paypal' ? 'di PayPal' : 'Nexi'}.
{submitting ? 'Un attimo…' : `Conferma e paga ${totale}€`}
setStep(3)}>← Torna indietro
)}
{step === 5 && (
Grazie! {libriScelti.length} libr{libriScelti.length === 1 ? 'o' : 'i'} per {nomeBambino} ({classe}) {libriScelti.length === 1 ? 'è stato registrato' : 'sono stati registrati'}.
Verranno consegnati a scuola al prossimo passaggio.
Fai un altro ordine
)}
);
}
function Hero() {
return (
Scegli i libri, in meno di un minuto.
Ordina e paga qui, i libri arrivano in classe.
);
}
function EmptyStateNoSchools() {
return (
Nessuna scuola attiva al momento
Il modulo riaprirà non appena verrà attivata la prossima raccolta ordini.
);
}
function ProgressDots({ step }) {
return {[1, 2, 3, 4].map((n) =>
)}
;
}
function StepCard({ eyebrow, title, children }) {
return (
{eyebrow}
{title}
{children}
);
}
function NavRow({ onBack, onNext, nextDisabled, nextLabel }) {
return (
{onBack ?
Indietro :
}
{nextLabel} →
);
}
function SummaryRow({ label, value, big }) {
return (
{label}
{value}
);
}
function WhatsAppBand() {
return (
💬
Preferisci scrivere? Ordina su WhatsApp in 30 secondi
→
);
}
// ============================================================
// ADMIN PANEL
// ============================================================
function AdminPanel({ istituti, setIstituti, libri, setLibri, volantini, setVolantini, ordini, setOrdini, paymentLinks, setPaymentLinks }) {
const [tab, setTab] = useState('scuole');
return (
setTab('scuole')} label="Scuole attive" />
setTab('libri')} label="Catalogo libri" />
setTab('volantini')} label="Volantini" />
setTab('pagamenti')} label="Pagamenti" />
setTab('ordini')} label={`Ordini (${ordini.length})`} />
setTab('impostazioni')} label="Impostazioni" />
{tab === 'scuole' &&
}
{tab === 'libri' &&
}
{tab === 'volantini' &&
}
{tab === 'pagamenti' &&
}
{tab === 'ordini' &&
}
{tab === 'impostazioni' && (
)}
);
}
function AdminTabBtn({ active, onClick, label }) {
return {label} ;
}
function SchoolsAdmin({ istituti, setIstituti, volantini }) {
const [nome, setNome] = useState('');
const [comune, setComune] = useState('');
const [via, setVia] = useState('');
const [cap, setCap] = useState('');
const [plessi, setPlessi] = useState([]);
const [nuovoPlessoNome, setNuovoPlessoNome] = useState('');
const [nuovoPlessoTipo, setNuovoPlessoTipo] = useState('infanzia');
const [volantinoId, setVolantinoId] = useState(volantini[0]?.id || '');
const [editId, setEditId] = useState(null);
function resetForm() {
setNome(''); setComune(''); setVia(''); setCap(''); setPlessi([]);
setNuovoPlessoNome(''); setNuovoPlessoTipo('infanzia');
setVolantinoId(volantini[0]?.id || ''); setEditId(null);
}
function startEdit(ic) {
setEditId(ic.id); setNome(ic.nome); setComune(ic.comune); setVia(ic.via || ''); setCap(ic.cap || '');
setPlessi(ic.plessi || []); setVolantinoId(ic.volantinoId || volantini[0]?.id || '');
}
function aggiungiPlesso() {
if (!nuovoPlessoNome.trim()) return;
setPlessi((prev) => [...prev, { nome: nuovoPlessoNome.trim(), tipo: nuovoPlessoTipo }]);
setNuovoPlessoNome('');
}
function rimuoviPlesso(idx) {
setPlessi((prev) => prev.filter((_, i) => i !== idx));
}
async function salva() {
if (!nome.trim() || !comune.trim() || plessi.length === 0) return;
if (editId) {
const next = istituti.map((i) => i.id === editId
? { ...i, nome: nome.trim(), comune: comune.trim(), via: via.trim(), cap: cap.trim(), plessi, volantinoId }
: i);
await setIstituti(next);
} else {
const nuovo = { id: 'ic-' + Date.now(), nome: nome.trim(), comune: comune.trim(), via: via.trim(), cap: cap.trim(), plessi, volantinoId };
await setIstituti([...istituti, nuovo]);
}
resetForm();
}
async function rimuovi(id) {
await setIstituti(istituti.filter((i) => i.id !== id));
if (editId === id) resetForm();
}
async function toggleAttivo(id) {
const next = istituti.map((i) => i.id === id ? { ...i, attivo: i.attivo === false ? true : false } : i);
await setIstituti(next);
}
return (
I tuoi istituti
Disattiva un istituto a fine campagna per non doverlo reinserire l'anno dopo.
{istituti.length === 0 &&
Nessun istituto inserito.
}
{istituti.map((ic) => {
const v = volantini.find((vv) => vv.id === ic.volantinoId);
const infanzia = (ic.plessi || []).filter((p) => p.tipo === 'infanzia');
const primMedia = (ic.plessi || []).filter((p) => p.tipo === 'primaria_media');
const attivo = ic.attivo !== false;
return (
{ic.nome} {!attivo && disattivato }
{ic.comune} {ic.cap ? `· ${ic.cap}` : ''}
{ic.via &&
Segreteria: {ic.via}
}
{infanzia.length > 0 &&
🧸 Infanzia: {infanzia.map((p) => p.nome).join(' · ')}
}
{primMedia.length > 0 &&
🎒 Primaria/Media: {primMedia.map((p) => p.nome).join(' · ')}
}
📋 {v ? v.nome : 'Nessun volantino assegnato'}
toggleAttivo(ic.id)}>
{attivo ? 'Disattiva' : 'Riattiva'}
startEdit(ic)}>Modifica
rimuovi(ic.id)}>Elimina
);
})}
{editId ? 'Modifica istituto' : 'Aggiungi nuovo istituto'}
Nome istituto comprensivo
setNome(e.target.value)} placeholder="Es. Area Alpi" />
Comune
setComune(e.target.value)} placeholder="Es. Sarzana" />
Via della segreteria
setVia(e.target.value)} placeholder="Es. Via Bertoloni 11" />
CAP
setCap(e.target.value)} placeholder="Es. 19038" />
Plessi
{plessi.length > 0 && (
{plessi.map((p, idx) => (
{p.tipo === 'infanzia' ? '🧸' : '🎒'}
{p.nome}
rimuoviPlesso(idx)}>✕
))}
)}
setNuovoPlessoNome(e.target.value)}
placeholder="Nome plesso, es. Primaria Falcone"
onKeyDown={(e) => e.key === 'Enter' && (e.preventDefault(), aggiungiPlesso())}
/>
setNuovoPlessoTipo(e.target.value)}>
Infanzia
Primaria/Media
+
Volantino da mostrare ai genitori
{volantini.length === 0 ? (
Crea prima un volantino nella tab "Volantini".
) : (
setVolantinoId(e.target.value)}>
{volantini.map((v) => {v.nome} )}
)}
{editId ? 'Salva modifiche' : 'Aggiungi istituto'}
{editId && Annulla modifica }
);
}
function BooksAdmin({ libri, setLibri }) {
const [titolo, setTitolo] = useState('');
const [descrizione, setDescrizione] = useState('');
const [pagine, setPagine] = useState('');
const [prezzo, setPrezzo] = useState('');
const [scorte, setScorte] = useState('');
const [copertina, setCopertina] = useState('📘');
const [copertinaImg, setCopertinaImg] = useState(null);
const [fasceEta, setFasceEta] = useState(['infanzia', 'primaria_media']);
const [editId, setEditId] = useState(null);
const [fileError, setFileError] = useState('');
const fileInputRef = useRef(null);
function resetForm() {
setTitolo(''); setDescrizione(''); setPagine(''); setPrezzo(''); setScorte(''); setCopertina('📘'); setCopertinaImg(null);
setFasceEta(['infanzia', 'primaria_media']); setEditId(null); setFileError('');
}
function startEdit(l) {
setEditId(l.id); setTitolo(l.titolo); setDescrizione(l.descrizione || ''); setPagine(l.pagine || '');
setPrezzo(l.prezzo); setScorte(l.scorte ?? ''); setCopertina(l.copertina || '📘'); setCopertinaImg(l.copertinaImg || null);
setFasceEta(l.fasceEta && l.fasceEta.length > 0 ? l.fasceEta : ['infanzia', 'primaria_media']);
}
function toggleFascia(f) {
setFasceEta((prev) => prev.includes(f) ? prev.filter((x) => x !== f) : [...prev, f]);
}
function handleFile(e) {
const file = e.target.files?.[0];
if (!file) return;
setFileError('');
const reader = new FileReader();
reader.onload = () => {
const img = new Image();
img.onload = () => {
const MAX_DIM = 500;
let { width, height } = img;
if (width > height && width > MAX_DIM) { height = Math.round(height * (MAX_DIM / width)); width = MAX_DIM; }
else if (height > MAX_DIM) { width = Math.round(width * (MAX_DIM / height)); height = MAX_DIM; }
const canvas = document.createElement('canvas');
canvas.width = width; canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
let quality = 0.82;
let dataUrl = canvas.toDataURL('image/jpeg', quality);
while (dataUrl.length > 180000 && quality > 0.3) {
quality -= 0.1;
dataUrl = canvas.toDataURL('image/jpeg', quality);
}
if (dataUrl.length > 180000) {
setFileError('Immagine troppo pesante anche dopo la compressione: prova con una foto più semplice.');
return;
}
setCopertinaImg(dataUrl);
};
img.onerror = () => setFileError('Non riesco a leggere questa immagine, riprova con un altro file.');
img.src = reader.result;
};
reader.onerror = () => setFileError('Errore durante la lettura del file.');
reader.readAsDataURL(file);
}
async function salva() {
if (!titolo.trim() || !prezzo || fasceEta.length === 0) return;
if (editId) {
const next = libri.map((l) => l.id === editId ? {
...l, titolo: titolo.trim(), descrizione: descrizione.trim(), pagine: pagine ? Number(pagine) : null,
prezzo: Number(prezzo), scorte: scorte === '' ? 9999 : Number(scorte), copertina, copertinaImg, fasceEta,
} : l);
await setLibri(next);
} else {
const nuovo = {
id: 'l-' + Date.now(), titolo: titolo.trim(), descrizione: descrizione.trim(),
pagine: pagine ? Number(pagine) : null, prezzo: Number(prezzo),
scorte: scorte === '' ? 9999 : Number(scorte), venduti: 0, copertina, copertinaImg, fasceEta,
};
await setLibri([...libri, nuovo]);
}
resetForm();
}
async function rimuovi(id) { await setLibri(libri.filter((l) => l.id !== id)); }
async function toggleAttivo(id) {
const next = libri.map((l) => l.id === id ? { ...l, attivo: l.attivo === false ? true : false } : l);
await setLibri(next);
}
return (
Catalogo attuale
{libri.map((l) => {
const disponibili = (l.scorte ?? 9999) - (l.venduti || 0);
const fasce = l.fasceEta && l.fasceEta.length > 0 ? l.fasceEta : ['infanzia', 'primaria_media'];
const etichettaFasce = fasce.length === 2 ? 'Infanzia + Primaria/Media'
: fasce.includes('infanzia') ? 'Solo Infanzia' : 'Solo Primaria/Media';
const attivo = l.attivo !== false;
return (
{l.copertinaImg ?
:
{l.copertina} }
{l.titolo} {!attivo && disattivato }
{l.prezzo}€ · {disponibili <= 0 ? 'Esaurito' : `${disponibili} disponibili`} (venduti {l.venduti || 0}/{l.scorte ?? '∞'})
🎯 {etichettaFasce}
toggleAttivo(l.id)}>
{attivo ? 'Disattiva' : 'Riattiva'}
startEdit(l)}>Modifica
rimuovi(l.id)}>Elimina
);
})}
{editId ? 'Modifica libro' : 'Aggiungi libro'}
Titolo
setTitolo(e.target.value)} placeholder="Titolo del libro" />
Descrizione breve
);
}
function FlyersAdmin({ volantini, setVolantini, libri }) {
const [nome, setNome] = useState('');
const [libriIds, setLibriIds] = useState([]);
const [editId, setEditId] = useState(null);
function resetForm() { setNome(''); setLibriIds([]); setEditId(null); }
function startEdit(v) { setEditId(v.id); setNome(v.nome); setLibriIds(v.libriIds); }
function toggleLibro(id) { setLibriIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]); }
async function salva() {
if (!nome.trim() || libriIds.length === 0) return;
if (editId) {
const next = volantini.map((v) => v.id === editId ? { ...v, nome: nome.trim(), libriIds } : v);
await setVolantini(next);
} else {
await setVolantini([...volantini, { id: 'v-' + Date.now(), nome: nome.trim(), libriIds }]);
}
resetForm();
}
async function rimuovi(id) {
await setVolantini(volantini.filter((v) => v.id !== id));
if (editId === id) resetForm();
}
return (
Volantini creati
Ogni volantino è un gruppo di libri da assegnare a uno o più istituti nella tab "Scuole attive".
{volantini.length === 0 &&
Nessun volantino ancora.
}
{volantini.map((v) => (
{v.nome}
{v.libriIds.length} libri
{v.libriIds.map((id) => libri.find((l) => l.id === id)?.titolo).filter(Boolean).join(' · ')}
startEdit(v)}>Modifica
rimuovi(v.id)}>Rimuovi
))}
{editId ? 'Modifica volantino' : 'Crea nuovo volantino'}
Nome volantino
setNome(e.target.value)} placeholder="Es. Volantino Primarie — Ottobre" />
Libri inclusi
{libri.length === 0 ? (
Aggiungi prima dei libri nella tab "Catalogo libri".
) : (
{libri.map((l) => (
toggleLibro(l.id)} />
{l.titolo} — {l.prezzo}€
))}
)}
{editId ? 'Salva modifiche' : 'Crea volantino'}
{editId && Annulla modifica }
);
}
function PaymentsAdmin({ paymentLinks, setPaymentLinks }) {
const [nexi, setNexi] = useState(paymentLinks.nexi || '');
const [paypal, setPaypal] = useState(paymentLinks.paypal || '');
const [saved, setSaved] = useState(false);
async function salva() {
await setPaymentLinks({ nexi: nexi.trim(), paypal: paypal.trim() });
setSaved(true);
setTimeout(() => setSaved(false), 2000);
}
return (
Link di pagamento
Incolla qui i tuoi link Pay-by-Link. Il genitore verrà reindirizzato al pagamento dopo aver confermato l'ordine.
Link Pay-by-Link Nexi
setNexi(e.target.value)} placeholder="https://pay.nexigroup.com/..." />
Include automaticamente carte, PostePay, Apple Pay e Google Pay.
Link PayPal
setPaypal(e.target.value)} placeholder="https://paypal.me/..." />
Salva link
{saved &&
Salvato ✓
}
);
}
function OrdersAdmin({ ordini, setOrdini }) {
const [filtroIc, setFiltroIc] = useState('tutti');
const [filtroPlesso, setFiltroPlesso] = useState('tutti');
const istitutiPresenti = [...new Set(ordini.map((o) => o.istitutoNome))];
const plessiPresenti = [...new Set(
ordini.filter((o) => filtroIc === 'tutti' || o.istitutoNome === filtroIc).map((o) => o.plesso)
)];
const filtrati = ordini
.filter((o) => filtroIc === 'tutti' || o.istitutoNome === filtroIc)
.filter((o) => filtroPlesso === 'tutti' || o.plesso === filtroPlesso);
function cambiaFiltroIc(valore) {
setFiltroIc(valore);
setFiltroPlesso('tutti');
}
async function segnaPagato(id) { await setOrdini(ordini.map((o) => o.id === id ? { ...o, stato: 'pagato' } : o)); }
async function segnaConsegnato(id) { await setOrdini(ordini.map((o) => o.id === id ? { ...o, stato: 'consegnato' } : o)); }
const totaleIncassato = ordini.filter((o) => o.stato !== 'da pagare').reduce((s, o) => s + (o.totale ?? o.prezzo ?? 0), 0);
return (
{ordini.length}
Ordini totali
{totaleIncassato}€
Incassato
cambiaFiltroIc(e.target.value)}>
Tutti gli istituti
{istitutiPresenti.map((n) => {n} )}
setFiltroPlesso(e.target.value)}>
Tutti i plessi
{plessiPresenti.map((n) => {n} )}
{filtrati.length === 0 ? (
Nessun ordine ancora. Appena un genitore completa il modulo, comparirà qui.
) : (
{filtrati.map((o) => {
const listaLibri = o.libri ? o.libri.map((l) => l.titolo).join(', ') : (o.libroTitolo || '');
const totale = o.totale ?? o.prezzo ?? 0;
return (
{o.bambino} — {o.classe}
{o.istitutoNome}{o.tipoScuola ? ` (${o.tipoScuola})` : ''} · {o.plesso}
{listaLibri} · {totale}€ · {o.metodoPagamento === 'paypal' ? 'PayPal' : 'Nexi'}{o.telefono ? ` · 📞 ${o.telefono}` : ''}
{o.stato}
{o.stato === 'da pagare' && segnaPagato(o.id)}>Segna pagato }
{o.stato === 'pagato' && segnaConsegnato(o.id)}>Segna consegnato }
);
})}
)}
);
}
function SettingsAdmin({ setIstituti, setLibri, setVolantini, setOrdini, setPaymentLinks }) {
const [confirming, setConfirming] = useState(false);
const [done, setDone] = useState(false);
async function ripristina() {
await Promise.all([
setIstituti(DEMO_ISTITUTI),
setLibri(DEMO_LIBRI),
setVolantini(DEMO_VOLANTINI),
]);
setConfirming(false);
setDone(true);
setTimeout(() => setDone(false), 2500);
}
async function svuotaOrdini() {
await setOrdini([]);
}
return (
Dati di esempio
Ripristina scuole, catalogo libri e volantini ai dati di esempio. Gli ordini non vengono toccati.
{!confirming ? (
setConfirming(true)}>
Ripristina dati di esempio
) : (
Questo sovrascriverà scuole, libri e volantini attualmente salvati. Confermi?
Sì, ripristina
setConfirming(false)}>Annulla
)}
{done &&
Dati ripristinati ✓
}
Ordini
Svuota la lista ordini (es. a inizio nuova campagna). Non si può annullare.
Svuota lista ordini
);
}
// ============================================================
// STYLES
// ============================================================
const colors = {
cream: '#FBF6EC', creamDeep: '#F2E9D8', ink: '#1F2D3D', inkSoft: '#4A5568',
coral: '#E2543C', coralDeep: '#C43F2A', gold: '#D9A441', line: '#E4DAC4',
white: '#FFFFFF', green: '#3E7A52',
};
const styles = {
app: { minHeight: '100vh', background: colors.cream, fontFamily: "'Inter', sans-serif", color: colors.ink },
loadingScreen: { minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: colors.cream, gap: 12 },
loadingMark: { fontSize: 40 },
loadingText: { fontFamily: "'Fraunces', serif", color: colors.inkSoft, fontSize: 16 },
topbar: { position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px', borderBottom: `1px solid ${colors.line}`, background: colors.white },
brand: { display: 'flex', alignItems: 'center', gap: 8 },
brandMark: { fontSize: 20 },
brandText: { fontFamily: "'Fraunces', serif", fontWeight: 600, fontSize: 17, color: colors.ink },
topbarActions: { display: 'flex', gap: 8 },
ghostBtn: { padding: '8px 14px', borderRadius: 8, border: `1px solid ${colors.line}`, background: 'transparent', color: colors.ink, fontSize: 13, cursor: 'pointer', fontWeight: 600 },
ghostBtnSmall: { padding: '6px 12px', borderRadius: 20, border: `1px solid ${colors.line}`, background: 'transparent', color: colors.inkSoft, fontSize: 12, cursor: 'pointer' },
gateOverlay: { position: 'fixed', inset: 0, background: 'rgba(31,45,61,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 50 },
gateCard: { background: colors.white, borderRadius: 16, padding: 28, width: 260, boxShadow: '0 20px 60px rgba(31,45,61,0.25)', textAlign: 'center' },
gateTitle: { fontFamily: "'Fraunces', serif", fontWeight: 600, fontSize: 16, marginBottom: 14 },
gateInput: { width: '100%', padding: '10px 12px', borderRadius: 8, border: `1.5px solid ${colors.line}`, fontSize: 16, textAlign: 'center', boxSizing: 'border-box', marginBottom: 8, fontFamily: "'Inter', sans-serif" },
gateError: { color: colors.coralDeep, fontSize: 12, marginBottom: 8 },
gateBtn: { width: '100%', padding: '10px 0', borderRadius: 8, border: 'none', background: colors.ink, color: colors.cream, fontWeight: 600, cursor: 'pointer', fontSize: 14 },
gateHint: { fontSize: 11, color: colors.inkSoft, marginTop: 10, opacity: 0.6 },
parentWrap: { maxWidth: 560, margin: '0 auto', padding: '0 16px 60px' },
hero: { position: 'relative', padding: '36px 4px 20px', overflow: 'hidden' },
heroBookmark: { position: 'absolute', top: 0, right: 24, width: 28, height: 56, background: colors.coral, clipPath: 'polygon(0 0, 100% 0, 100% 100%, 50% 78%, 0 100%)' },
heroTitle: { fontFamily: "'Fraunces', serif", fontWeight: 600, fontSize: 30, lineHeight: 1.15, color: colors.ink, maxWidth: 380 },
heroSub: { marginTop: 10, fontSize: 14.5, color: colors.inkSoft, maxWidth: 380, lineHeight: 1.5 },
emptyState: { position: 'relative', padding: '60px 4px', textAlign: 'center' },
progressRow: { display: 'flex', gap: 6, padding: '4px 4px 18px' },
progressDot: { height: 4, flex: 1, borderRadius: 4, background: colors.line },
progressDotActive: { background: colors.coral },
cardSequence: {},
stepCard: { background: colors.white, borderRadius: 18, padding: '24px 22px', boxShadow: '0 1px 3px rgba(31,45,61,0.06)', border: `1px solid ${colors.line}` },
eyebrow: { fontSize: 11.5, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: colors.coral, marginBottom: 6 },
stepTitle: { fontFamily: "'Fraunces', serif", fontWeight: 600, fontSize: 21, marginBottom: 18, color: colors.ink },
choiceGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 },
choiceBtn: { textAlign: 'left', padding: '14px 14px', borderRadius: 12, border: `1.5px solid ${colors.line}`, background: colors.cream, cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 2, fontFamily: "'Inter', sans-serif" },
choiceBtnActive: { border: `1.5px solid ${colors.coral}`, background: '#FCEEE9' },
choiceBtnTitle: { fontWeight: 600, fontSize: 14.5, color: colors.ink },
choiceBtnSub: { fontSize: 12, color: colors.inkSoft },
fieldLabel: { fontSize: 13, fontWeight: 600, color: colors.ink, marginBottom: 8 },
optionalTag: { fontWeight: 400, color: colors.inkSoft, fontSize: 12 },
requiredTag: { fontWeight: 600, color: colors.coralDeep, fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.03em' },
textInput: { width: '100%', padding: '13px 14px', borderRadius: 10, border: `1.5px solid ${colors.line}`, fontSize: 16, boxSizing: 'border-box', fontFamily: "'Inter', sans-serif", background: colors.cream, color: colors.ink },
textInputError: { border: `1.5px solid ${colors.coralDeep}`, background: '#FCEEE9' },
errorMsg: { fontSize: 12, color: colors.coralDeep, marginTop: 6, fontWeight: 600 },
textArea: { width: '100%', padding: '13px 14px', borderRadius: 10, border: `1.5px solid ${colors.line}`, fontSize: 14, boxSizing: 'border-box', fontFamily: "'Inter', sans-serif", background: colors.cream, resize: 'vertical', color: colors.ink },
flyerGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 },
flyerCard: { position: 'relative', textAlign: 'left', padding: '14px 10px 12px', borderRadius: 14, border: `1.5px solid ${colors.line}`, background: colors.cream, cursor: 'pointer', fontFamily: "'Inter', sans-serif", display: 'flex', flexDirection: 'column', gap: 3 },
flyerCardActive: { border: `1.5px solid ${colors.coral}`, background: '#FCEEE9' },
flyerCardDisabled: { opacity: 0.45, cursor: 'not-allowed' },
flyerPriceTag: { position: 'absolute', top: -8, right: -8, background: colors.coral, color: colors.white, fontFamily: "'Fraunces', serif", fontWeight: 700, fontSize: 14, width: 34, height: 34, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 2px 6px rgba(226,84,60,0.35)', transform: 'rotate(8deg)' },
flyerEmoji: { fontSize: 44, marginBottom: 4, textAlign: 'center', width: '100%' },
flyerImg: { width: '100%', height: 200, objectFit: 'contain', objectPosition: 'center', borderRadius: 8, marginBottom: 6, background: colors.white },
flyerTitle: { fontWeight: 600, fontSize: 12, color: colors.ink, lineHeight: 1.25 },
flyerDesc: { fontSize: 10, color: colors.inkSoft, lineHeight: 1.35, opacity: 0.85 },
flyerPages: { fontSize: 9.5, color: colors.inkSoft, opacity: 0.55 },
flyerCheckZone: { marginTop: 'auto', paddingTop: 8, display: 'flex', justifyContent: 'flex-end' },
flyerCheckbox: { width: 26, height: 26, borderRadius: 6, border: `2px solid ${colors.line}`, background: colors.white, display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: 15, color: colors.coral },
flyerCheckboxActive: { border: `2px solid ${colors.coral}`, background: '#FDE3DC' },
flyerSoldOut: { fontSize: 11, fontWeight: 700, color: colors.coralDeep, textTransform: 'uppercase', letterSpacing: '0.03em' },
selectionBar: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 16, padding: '12px 16px', borderRadius: 10, background: colors.ink, color: colors.cream, fontSize: 13.5, fontWeight: 600 },
selectionTotal: { fontFamily: "'Fraunces', serif", fontWeight: 700, fontSize: 18, color: colors.coral },
navRow: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 22 },
backBtn: { padding: '11px 16px', borderRadius: 10, border: `1.5px solid ${colors.line}`, background: 'transparent', color: colors.inkSoft, fontWeight: 600, cursor: 'pointer', fontSize: 14 },
nextBtn: { padding: '12px 20px', borderRadius: 10, border: 'none', background: colors.ink, color: colors.cream, fontWeight: 700, cursor: 'pointer', fontSize: 14.5 },
nextBtnDisabled: { opacity: 0.35, cursor: 'not-allowed' },
summaryBox: { background: colors.cream, borderRadius: 12, padding: '16px 18px', marginBottom: 16 },
summaryRow: { display: 'flex', justifyContent: 'space-between', padding: '7px 0', fontSize: 14 },
summaryLabel: { color: colors.inkSoft },
summaryValue: { fontWeight: 600, color: colors.ink, textAlign: 'right' },
summaryValueBig: { fontFamily: "'Fraunces', serif", fontWeight: 700, fontSize: 22, color: colors.coralDeep },
summaryDivider: { height: 1, background: colors.line, margin: '6px 0' },
payMethodGrid: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 16 },
payMethodBtn: { textAlign: 'left', padding: '14px 16px', borderRadius: 12, border: `1.5px solid ${colors.line}`, background: colors.cream, cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 3, fontFamily: "'Inter', sans-serif" },
payMethodBtnActive: { border: `1.5px solid ${colors.coral}`, background: '#FCEEE9' },
payMethodTitle: { fontWeight: 600, fontSize: 14.5, color: colors.ink },
payMethodSub: { fontSize: 12, color: colors.inkSoft },
paymentNote: { fontSize: 12.5, color: colors.inkSoft, lineHeight: 1.5, marginBottom: 18 },
payBtn: { width: '100%', padding: '15px 0', borderRadius: 12, border: 'none', background: colors.coral, color: colors.white, fontWeight: 700, fontSize: 16, cursor: 'pointer', fontFamily: "'Inter', sans-serif" },
backLink: { width: '100%', padding: '10px 0', background: 'transparent', border: 'none', color: colors.inkSoft, fontSize: 13, cursor: 'pointer', marginTop: 8 },
successText: { fontSize: 14.5, color: colors.inkSoft, lineHeight: 1.6, marginBottom: 20 },
waBand: { marginTop: 20, display: 'flex', alignItems: 'center', gap: 12, padding: '16px 18px', borderRadius: 14, background: '#E9F3EC', border: `1.5px solid #CFE6D5`, textDecoration: 'none', color: colors.ink, fontSize: 13.5 },
waIcon: { fontSize: 22 },
waArrow: { marginLeft: 'auto', color: colors.green, fontWeight: 700 },
adminWrap: { maxWidth: 980, margin: '0 auto', padding: '24px 20px 60px' },
adminTabs: { display: 'flex', gap: 8, marginBottom: 20, flexWrap: 'wrap' },
adminTabBtn: { padding: '9px 16px', borderRadius: 20, border: `1.5px solid ${colors.line}`, background: colors.white, color: colors.inkSoft, fontWeight: 600, fontSize: 13.5, cursor: 'pointer' },
adminTabBtnActive: { background: colors.ink, color: colors.cream, border: `1.5px solid ${colors.ink}` },
adminSection: {},
adminGrid: { display: 'grid', gridTemplateColumns: '1.1fr 0.9fr', gap: 24 },
adminCol: { background: colors.white, borderRadius: 16, padding: 22, border: `1px solid ${colors.line}` },
adminSectionTitle: { fontFamily: "'Fraunces', serif", fontWeight: 600, fontSize: 17, marginBottom: 6 },
adminHint: { fontSize: 12.5, color: colors.inkSoft, lineHeight: 1.5, marginBottom: 16 },
adminHintSmall: { fontSize: 11.5, color: colors.inkSoft, lineHeight: 1.4, marginTop: 6, opacity: 0.85 },
adminEmpty: { fontSize: 13, color: colors.inkSoft, padding: '20px 0', textAlign: 'center' },
adminFormActions: { marginTop: 18, display: 'flex', flexDirection: 'column', gap: 4 },
schoolList: { display: 'flex', flexDirection: 'column', gap: 10 },
schoolItem: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, padding: '12px 14px', borderRadius: 10, background: colors.cream, border: `1px solid ${colors.line}` },
schoolItemTitle: { fontWeight: 600, fontSize: 14 },
schoolItemSub: { fontSize: 12, color: colors.inkSoft, marginTop: 2 },
schoolItemPlessi: { fontSize: 11.5, color: colors.inkSoft, marginTop: 4, opacity: 0.8 },
schoolItemVolantino: { fontSize: 11.5, color: colors.coralDeep, marginTop: 6, fontWeight: 600 },
plessiList: { display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 10 },
plessoRow: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderRadius: 8, background: colors.cream, border: `1px solid ${colors.line}` },
plessoRowTipo: { fontSize: 15 },
plessoRowNome: { flex: 1, fontSize: 13, color: colors.ink, fontWeight: 500 },
plessoRowRemove: { border: 'none', background: 'transparent', color: colors.coralDeep, fontWeight: 700, cursor: 'pointer', fontSize: 13, padding: '2px 6px' },
addPlessoRow: { display: 'flex', gap: 8 },
tipoPlessoSelect: { padding: '12px 10px', borderRadius: 10, border: `1.5px solid ${colors.line}`, fontSize: 13, background: colors.cream, color: colors.ink, fontFamily: "'Inter', sans-serif" },
addPlessoBtn: { width: 44, borderRadius: 10, border: 'none', background: colors.ink, color: colors.cream, fontWeight: 700, fontSize: 18, cursor: 'pointer' },
schoolItemActions: { display: 'flex', flexDirection: 'column', gap: 6, flexShrink: 0 },
smallActionBtn: { padding: '6px 10px', borderRadius: 8, border: `1px solid ${colors.line}`, background: colors.white, fontSize: 11.5, cursor: 'pointer', fontWeight: 600, color: colors.ink },
smallActionBtnDanger: { padding: '6px 10px', borderRadius: 8, border: `1px solid #F0C9C0`, background: '#FCEEE9', fontSize: 11.5, cursor: 'pointer', fontWeight: 600, color: colors.coralDeep },
smallActionBtnPositive: { padding: '6px 10px', borderRadius: 8, border: `1px solid #CFE6D5`, background: '#E9F3EC', fontSize: 11.5, cursor: 'pointer', fontWeight: 600, color: colors.green },
schoolItemInactive: { opacity: 0.55 },
inactiveTag: { fontSize: 10.5, fontWeight: 700, color: colors.inkSoft, background: colors.line, padding: '2px 7px', borderRadius: 10, marginLeft: 6, textTransform: 'uppercase', letterSpacing: '0.03em' },
emojiBtn: { fontSize: 20, padding: '10px 0', borderRadius: 10, border: `1.5px solid ${colors.line}`, background: colors.cream, cursor: 'pointer', textAlign: 'center' },
bookThumb: { width: 40, height: 40, borderRadius: 8, objectFit: 'cover', border: `1px solid ${colors.line}` },
bookThumbBig: { width: 64, height: 64, borderRadius: 10, objectFit: 'cover', border: `1px solid ${colors.line}` },
bookThumbBigEmoji: { width: 64, height: 64, borderRadius: 10, background: colors.cream, border: `1px solid ${colors.line}`, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 30 },
checklistBox: { display: 'flex', flexDirection: 'column', gap: 8, maxHeight: 260, overflowY: 'auto', padding: '4px 2px' },
checklistItem: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 13.5, color: colors.ink, cursor: 'pointer' },
fasceEtaRow: { display: 'flex', gap: 16 },
fasciaCheckLabel: { display: 'flex', alignItems: 'center', gap: 7, fontSize: 13.5, color: colors.ink, cursor: 'pointer', fontWeight: 500 },
ordersHeader: { display: 'flex', alignItems: 'center', gap: 20, marginBottom: 18, flexWrap: 'wrap' },
ordersStat: { background: colors.white, border: `1px solid ${colors.line}`, borderRadius: 12, padding: '10px 18px', textAlign: 'center', minWidth: 100 },
ordersStatNum: { fontFamily: "'Fraunces', serif", fontWeight: 700, fontSize: 22, color: colors.coralDeep },
ordersStatLabel: { fontSize: 11, color: colors.inkSoft, marginTop: 2 },
filterSelect: { marginLeft: 'auto', padding: '10px 14px', borderRadius: 10, border: `1.5px solid ${colors.line}`, fontSize: 13, background: colors.white, color: colors.ink, fontFamily: "'Inter', sans-serif" },
ordersTable: { display: 'flex', flexDirection: 'column', gap: 10 },
orderRow: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, background: colors.white, border: `1px solid ${colors.line}`, borderRadius: 12, padding: '14px 18px', flexWrap: 'wrap' },
orderRowMain: {},
orderRowTitle: { fontWeight: 700, fontSize: 14.5, color: colors.ink },
orderRowSub: { fontSize: 12, color: colors.inkSoft, marginTop: 2 },
orderRowActions: { display: 'flex', alignItems: 'center', gap: 10 },
stateBadge: { fontSize: 11, fontWeight: 700, padding: '5px 10px', borderRadius: 20, background: '#FDF2E3', color: colors.gold, textTransform: 'uppercase', letterSpacing: '0.03em' },
stateBadgePaid: { background: '#E9F3EC', color: colors.green },
stateBadgeDelivered: { background: '#E8EEF4', color: colors.ink },
};
// ============================================================
// MOUNT
// ============================================================
// Wrapper fullscreen per uso dentro react-router (nasconde header/footer del sito)
export default function OrdiniPage() {
return (
);
}